﻿using System;
using System.Collections.Generic;
using System.Text;



namespace dllLink
{
    internal class Pricer
    {
        private Product mvProduct;
        private static double q99 = 2.58;

        public Pricer(Product theProduct)
        {
            mvProduct = theProduct;
        }


        // fonction de pricing optimisée pour l'absence de coûts de transaction
        public double[,] mPrice(DateTime thePricingDate, TimeSpan theRebalancingPeriod, ulong theNbDraws, TimeSpan theHistoryDepth)
        {
            int myNbAssets = mvProduct.mGetNbAssets();
            int myNbUnderlyings = mvProduct.mGetNbUnderlyings();
            int myNbExchangeRates = mvProduct.mGetNbExchangeRates();
            int myNbInterestRates = mvProduct.mGetNbInterestRates();
            bool[] myUnderlyingIsUSD = mvProduct.mUnderlyingIsUSD();
            
            //modélisation globale du produit
            VarCovarEstimator myAssetEstimator = new EmpiricalEstimator(mvProduct, thePricingDate, theHistoryDepth);
            VasicekEstimator[] myRateEstimator = new VasicekEstimator[2];
            myRateEstimator[0] = new VasicekEmpiricalEstimator(mvProduct.mGetRateName(0), thePricingDate, theHistoryDepth);
            myRateEstimator[1] = new VasicekEmpiricalEstimator(mvProduct.mGetRateName(1), thePricingDate, theHistoryDepth);
            GlobalModel myGlobalModel = new GlobalModel(myNbAssets, myNbUnderlyings, myNbExchangeRates, myNbInterestRates, myUnderlyingIsUSD);
            myGlobalModel.mSetParameters(myAssetEstimator, myRateEstimator);
            

            int myNbFixings = mvProduct.mGetNbFixingDates();
            int myNbPastFixings, myNbNextFixings;
            DateTime[] myPastFixings, myNextFixings;
            mSplitFixings(thePricingDate, out myNbPastFixings, out myPastFixings, out myNbNextFixings, out myNextFixings);
            double[,] myScenario = new double[myNbFixings, myNbUnderlyings];

            double[] myPriceNorm = new double[myNbUnderlyings];
            for (int i = 0; i < myNbUnderlyings; i++)
            {
                myPriceNorm[i] = mvProduct.mGetAssetNAV(i, myPastFixings[0]);
            }
            // remplit la partie réalisée des fixings
            for (int i = 0; i < myNbPastFixings; i++)
            {
                for (int j = 0; j < myNbUnderlyings; j++)
                {
                    myScenario[i, j] = mvProduct.mGetAssetNAV(j, myPastFixings[i]) / myPriceNorm[j];
                }
            }

            // repère les sous-jacents déjà cristallisés
            Boolean[] isCr = new Boolean[myNbAssets];
            double Vmax;
            int imax;
            for (int i = 1; i < myNbPastFixings; i++)
            {
                Vmax = 0;
                imax = 0;
                for (int j = 0; j < myNbUnderlyings; j++)
                {
                    if (!(isCr[j]))
                    {
                        if (myScenario[i, j] > Vmax)
                        {
                            Vmax = myScenario[i, j];
                            imax = j;
                        }
                    }
                }
                isCr[imax] = true;
            }

            int myResLength = 1+myNbUnderlyings+2;
            double[,] myRes = new double[myResLength,3]; //contiendra prix + couverture

            //(log)prix initiaux
            double[] myInitPrice = new double[myNbAssets];
            double[] myCurrentPrice = new double[myNbAssets];
            

            // initialisations
            for (int j = 0; j < myNbAssets; j++)
            {
                myInitPrice[j] = mvProduct.mGetAssetNAV(j, thePricingDate);
            }
            double myEURUSD = myInitPrice[myNbUnderlyings];
            // Pour les sous-jacents, on normalise à la valeur initiale
            for (int j = 0; j < myNbUnderlyings; j++)
            {
                myInitPrice[j] = myInitPrice[j] / myPriceNorm[j];
            }
            // !! Pour les sous-jacents et le change (pas les taux d'intérêts),
            // !! on travaille avec les logs de ces valeurs
            for (int j = 0; j < myNbUnderlyings+myNbExchangeRates; j++)
            {
                myInitPrice[j] = Math.Log(myInitPrice[j]);
            }

            //calcul du prix du produit
            double[] myAux = mMonteCarlo(myScenario, myNbPastFixings, myNbNextFixings, myNextFixings,
            theNbDraws, thePricingDate, myNbAssets, myNbUnderlyings, myGlobalModel, myInitPrice);
            myRes[0,0] = myAux[0];
            myRes[0,1] = myAux[0] - myAux[1];
            myRes[0,2] = myAux[0] + myAux[1];


            //estimation des deltas
            double[] myAuxU = new double[2];
            double[] myAuxL = new double[2];
            double[] myNewInitPrice;
            double[] myDeltaConfidence = new double[myNbUnderlyings];
            double coefEpsilon = 0.01;
            double epsilon;
            for (int j = 0; j < myNbUnderlyings; j++)
            {
                if (isCr[j])
                {
                    myRes[j + 1, 0] = 0;
                    myRes[j + 1, 1] = 0;
                    myRes[j + 1, 2] = 0;
                }
                else
                {
                    myNewInitPrice = myInitPrice;
                    epsilon = Math.Exp(myNewInitPrice[j]) * coefEpsilon;
                    myNewInitPrice[j] = Math.Log(Math.Exp(myNewInitPrice[j])+epsilon);
                    myAuxU = mMonteCarlo(myScenario, myNbPastFixings, myNbNextFixings, myNextFixings,
                    theNbDraws, thePricingDate, myNbAssets, myNbUnderlyings, myGlobalModel, myNewInitPrice);
                    
                    myNewInitPrice = myInitPrice;
                    myNewInitPrice[j] = Math.Log(Math.Exp(myNewInitPrice[j]) - epsilon);
                    myAuxL = mMonteCarlo(myScenario, myNbPastFixings, myNbNextFixings, myNextFixings,
                    theNbDraws, thePricingDate, myNbAssets, myNbUnderlyings, myGlobalModel, myNewInitPrice);
                    
                    myRes[j + 1, 0] = (myAuxU[0] - myAuxL[0]) / (2.0*epsilon);
                    myDeltaConfidence[j] = Math.Sqrt(Math.Pow(myAuxU[1], 2) + Math.Pow(myAuxL[1], 2)) / (2 * epsilon);
                    myRes[j + 1, 1] = myRes[j + 1, 0] - myDeltaConfidence[j];
                    myRes[j + 1, 2] = myRes[j + 1, 0] + myDeltaConfidence[j];
                }
            }

            // montant en cash € et en futures €/$, ainsi que leurs intervalles de confiances
            myRes[myNbUnderlyings + 1,0] = myRes[0,0];
            myRes[myNbUnderlyings + 1, 2] = 0.0;
            myRes[myNbUnderlyings + 2, 0] = 0.0;
            myRes[myNbUnderlyings + 2, 2] = 0.0;
            for (int j = 0; j < myNbUnderlyings; j++)
            {
                if (myUnderlyingIsUSD[j])
                {
                    myRes[myNbUnderlyings + 1, 0] -= myRes[j + 1, 0] / myEURUSD;
                    myRes[myNbUnderlyings + 1, 2] += Math.Pow(myDeltaConfidence[j] / myEURUSD,2);
                    myRes[myNbUnderlyings + 2, 0] += myRes[j + 1, 0] / myEURUSD;
                    myRes[myNbUnderlyings + 2, 2] += Math.Pow(myDeltaConfidence[j] / myEURUSD, 2);
                }
                else
                {
                    myRes[myNbUnderlyings + 1, 0] -= myRes[j + 1, 0];
                    myRes[myNbUnderlyings + 1, 2] += Math.Pow(myDeltaConfidence[j], 2);
                }
            }
            myRes[myNbUnderlyings + 1, 2] = Math.Sqrt(myRes[myNbUnderlyings + 1, 2]);
            myRes[myNbUnderlyings + 1, 1] = myRes[myNbUnderlyings + 1, 0] - myRes[myNbUnderlyings + 1, 2];
            myRes[myNbUnderlyings + 1, 2] += myRes[myNbUnderlyings + 1, 0];
            myRes[myNbUnderlyings + 2, 2] = Math.Sqrt(myRes[myNbUnderlyings + 2, 2]);
            myRes[myNbUnderlyings + 2, 1] = myRes[myNbUnderlyings + 2, 0] - myRes[myNbUnderlyings + 2, 2];
            myRes[myNbUnderlyings + 2, 2] += myRes[myNbUnderlyings + 2, 0];

            //normalisations
            for (int i = 0; i < myResLength; i++)
            {
                for (int j = 0; j < 3; j++)
                {
                    myRes[i, j] *= 100;
                    myRes[i, j] = Math.Round(myRes[i, j], 2);
                }
            }
            return myRes;
        }



        // boucle Monte Carlo principale
        private double[] mMonteCarlo(double[,] theScenario, int theNbPastFixings, int theNbNextFixings,
            DateTime[] theNextFixings, ulong theNbDraws, DateTime thePricingDate, int theNbAssets,
            int theNbUnderlyings, GlobalModel theGlobalModel, double[] theInitPrice)
        {
            double myMean = 0.0;
            double myVar = 0.0;
            double myPrice;
            double[] myRate = new double[1];
            DateTime myCurrentDate;
            DateTime myLastFixingDate = theNextFixings[theNbNextFixings - 1];
            double[] myCurrentPrice = new double[theNbAssets];
            int myFixingIndex = 0;
            int ii, jj;
            ulong m;
            GSLFunctions.initSeed();
            for (m = 0; m < theNbDraws; m++)
            {
                myCurrentDate = thePricingDate;
                myFixingIndex = 0;
                for (ii = 0; ii < theNbAssets; ii++)
                {
                    myCurrentPrice[ii] = theInitPrice[ii];
                }
                myCurrentDate = myCurrentDate.AddDays(1);
                // diffusion des prix
                theGlobalModel.mInitDiscount();
                while (myCurrentDate.CompareTo(myLastFixingDate) <= 0)
                {
                    theGlobalModel.mDiffuse(ref myCurrentPrice);
                    if (myCurrentDate.Equals(theNextFixings[myFixingIndex]))
                    {
                        for (jj = 0; jj < theNbUnderlyings; jj++)
                        {
                            theScenario[theNbPastFixings + myFixingIndex, jj] = Math.Exp(myCurrentPrice[jj]);
                        }
                        myFixingIndex += 1;
                    }
                    myCurrentDate = myCurrentDate.AddDays(1);
                }
                myPrice = mPayoff(theScenario);
                myPrice *= theGlobalModel.mGetDiscount(); // actualisation
                myMean += myPrice;
                myVar += myPrice * myPrice;
            }
            myMean /= theNbDraws;
            myVar = myVar / (theNbDraws - 1) - myMean * myMean;
            double[] myRes = new double[2];
            myRes[0] = myMean;
            myRes[1] = q99 * Math.Sqrt(myVar / theNbDraws);
            return myRes;
        }



        // sépare les dates de fixing entre les fixings réalisés et les fixings à venir
        private void mSplitFixings(DateTime thePricingDate, out int theNbPastFixings, out DateTime[] thePastFixings,
                                   out int theNbNextFixings, out DateTime[] theNextFixings)
        {
            DateTime[] myFixingDates = mvProduct.mGetFixingDates();
            int myNbFixings = mvProduct.mGetNbFixingDates();
            theNbPastFixings = 0;
            int myDiff = DateTime.Compare(thePricingDate, myFixingDates[theNbPastFixings]);
            while (myDiff > 0)
            {
                theNbPastFixings++;
                if (theNbPastFixings < myNbFixings)
                    { myDiff = DateTime.Compare(thePricingDate, myFixingDates[theNbPastFixings]); }
                else
                    { myDiff = 0; }
            }
            theNbNextFixings = myNbFixings - theNbPastFixings;
            thePastFixings = new DateTime[theNbPastFixings];
            theNextFixings = new DateTime[theNbNextFixings];
            for (int i = 0; i < theNbPastFixings; i++)
                { thePastFixings[i] = myFixingDates[i]; }
            for (int i = 0; i < theNbNextFixings; i++)
                { theNextFixings[i] = myFixingDates[theNbPastFixings + i]; }
        }



        // donne le payoff du produit pour un scénario donné sur les sous-jacents
        private double mPayoff(double[,] theScenario)
        {
            int N = theScenario.GetLength(1); // Nombre de sous jacents 
            int Y = theScenario.GetLength(0);// Nombre d'années on l’on constate les valeurs (année 0 + 10 autres)
            double[] V = new double[Y]; // valeur du panier aux différentes échéances 
            double[] perf = new double[Y];
            double[] w = mvProduct.mGetWeights();
            bool[] isCr = new bool[N];
            int[] Cr = new int[N]; // cr[i]=année ou le fond i est cristallisé
            double Vmax, Pmax;
            int imax = 0;
            // Initialisations. 
            V[0] = 1;

            // Calcul des résultats année par année et cristallisation.
            // Parcours des années. 
            for (int i = 1; i < Y; i++)
            {
                V[i] = 0; Vmax = 0; imax = 0;
                for (int j = 0; j < N; j++)
                {
                    if (isCr[j])
                    {
                        // Si valeur cristallisée on récupère son cours en date de cristallisation.
                        V[i] += w[j] * theScenario[Cr[j],j];
                    }
                    else
                    {
                        // sinon on prend la dernière valeur de cotation.
                        V[i] += w[j] * theScenario[i,j];
                        if (theScenario[i,j] > Vmax)
                        {
                            Vmax = theScenario[i, j]; 
                            imax = j;
                        }
                    }
                }
                // cristallisation de l'OPCVM non cristallisée
                // ayant eu la meilleure performance.
                Cr[imax] = i; 
                isCr[imax] = true;
                perf[i] = (V[i] - V[0]) / V[0];
            }
            // Détermination du meilleur résultat
            Pmax = perf[0]; //100 
            for (int i = 0; i < Y; i++)
            {
                if (perf[i] > Pmax)
                {
                    Pmax = perf[i];
                }
            }
            return Math.Max(1,1 + 0.9 * Pmax);
        }
    }
}
